package com.laobiao.file_netty_lib.netty.client;

import java.io.File;
import java.util.ArrayDeque;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.laobiao.file_netty_lib.content.FileContentApplication;
import com.laobiao.file_netty_lib.content.LoggerUtil;
import com.laobiao.file_netty_lib.exception.FileBaseException;
import com.laobiao.file_netty_lib.netty.client.handler.ClientHandler;
import com.laobiao.file_netty_lib.netty.client.handler.ClientMkdirBusiness;
import com.laobiao.file_netty_lib.netty.client.handler.fileList.ClientFileListBusiness;
import com.laobiao.file_netty_lib.netty.client.handler.fileList.FileDetail;
import com.laobiao.file_netty_lib.netty.client.handler.ClientDataUploadBusiness;
import com.laobiao.file_netty_lib.netty.client.handler.ClientFileDeleteBusiness;
import com.laobiao.file_netty_lib.netty.client.handler.ClientFileDownloadBusiness;
import com.laobiao.file_netty_lib.netty.client.handler.ClientFileUploadBusiness;
import com.laobiao.file_netty_lib.packet.command.ResoultCommand;
import com.laobiao.file_netty_lib.packet.command.Mkdir.MkdirCommand;
import com.laobiao.file_netty_lib.packet.command.download.ClientFileDownloadCommand;
import com.laobiao.file_netty_lib.packet.command.fileDelete.FileDeleteCommand;
import com.laobiao.file_netty_lib.packet.command.filelist.FileListCommand;
import com.laobiao.file_netty_lib.packet.data.BaseDataPacket;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class FileClient {
	private String remoteHost;
	private int remotePort;
	private Integer localPort;
	private ChannelFuture channelFuture;
	private EventLoopGroup workerGroup;
	private int defaultTimeoutSeconde=20;
	public boolean status =false;
	private static final Logger logger=LoggerUtil.getLogger(FileClient.class);
	/**
	 * 正在执行的业务命令缓存
	 * key: /ip:端口_业务组
	 */
	private final static ConcurrentHashMap<String, ClientBusiness> businessPackets=new ConcurrentHashMap<String, ClientBusiness>();
	
	/**
	 * 设置连接信息
	 * 
	 * @param remoteHost 服务端地址
	 * @param remotePort 服务端端口
	 * @param localPort  绑定本地端口，为null时由系统自动生成
	 */
	public FileClient(String remoteHost, int remotePort, Integer localPort) {
		if (remoteHost == null || "".equals(remoteHost.trim())) {
			throw new FileBaseException("服务端地址不能为空！", FileBaseException.ERR_PARAM_NULL);
		}
		this.remoteHost = remoteHost;
		this.remotePort = remotePort;
		this.localPort = localPort;
		workerGroup = new NioEventLoopGroup();
		Bootstrap strap = new Bootstrap(); // 构建引导
		strap.group(workerGroup); // 设置事件管理组
		strap.channel(NioSocketChannel.class); // 设置为TCP连接
		strap.option(ChannelOption.SO_KEEPALIVE, true); // 长连接检测
		strap.option(ChannelOption.SO_REUSEADDR, true);// 公用端口
		strap.handler(new ClientInitializer(new ClientHandler()));
		if(localPort!=null && localPort>=0) {
			strap.localAddress(localPort);
		}
		try {
			channelFuture=strap.connect(remoteHost, remotePort).sync();
			status =true;
//			channelFuture=strap.connect(remoteHost, remotePort).await();
		} catch (Exception e) {
			// TODO: handle exception
			logger.log(Level.WARNING, "启动客户端失败！", e);
			close();
		}
	}

	public void close() {
		try {
			channelFuture.channel().close().sync();// 等待关闭
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			workerGroup.shutdownGracefully();// 关闭工作管理组
		}
		logger.info("文件客户端关闭！");
	}

	/**
	 * 上传目录
	 * @param dir 本地目录地址
	 * @param remoteDir 上传的目录地址 
	 * @return
	 */
	public int uploadDir(File dir, String remoteDir) {
		ArrayDeque<File> queDeque = new ArrayDeque<File>();
		queDeque.offer(dir);
		String dirPath=dir.getAbsolutePath();
		remoteDir=remoteDir.replace("/", "\\");
		if(remoteDir.endsWith("\\")) {
			remoteDir=remoteDir.substring(0, remoteDir.length()-1);
		}
		int uploadSize=0;
		while (queDeque.size() > 0) {
			File f = queDeque.poll();
			if (f.exists() && f.isDirectory()) {
				File[] childrens = f.listFiles();
				for (File c : childrens) {
					if(c.isFile()) {
//						logger.info("开始上传文件:"+c);
						try {
							if(uploadFile(c,remoteDir+c.getAbsolutePath().replace(dirPath, ""))) {
								uploadSize++;
							}else {
								logger.log(Level.WARNING, "文件上传失败");
							}
							
						} catch (Exception e) {
							// TODO: handle exception
							logger.log(Level.WARNING, "文件上传失败", e);
						}
						
					}else {
						queDeque.offer(c);
					}
				}
			}
		}
		return uploadSize;
	}
	
	/**
	 * 上传文件
	 * @param localFile
	 * @param remoteFile
	 * @return
	 */
	public boolean uploadFile(File localFile, String remoteFile) throws FileBaseException{
		if(localFile==null || !localFile.exists()) {
			throw new FileBaseException("上传的本地文件不存在！", FileBaseException.ERR_PARAM_NULL);
		}
		if(!localFile.isFile()) {
			throw new FileBaseException(localFile.getAbsolutePath()+" 不是文件！", FileBaseException.ERR_PARAM_WRONG);
		}
		if(remoteFile==null || "".equals(remoteFile.trim())) {
			throw new FileBaseException("上传到远程的文件路径不能为空！", FileBaseException.ERR_PARAM_NULL);
		}
		Channel channel = channelFuture.channel();
		String remoteAddressString=channel.remoteAddress().toString();
		int businessGroup = FileContentApplication.getLastBusinessNumber();
		ClientFileUploadBusiness uploadBusiness = new ClientFileUploadBusiness(localFile, remoteFile, businessGroup,channel);
		addCommandBusiness(remoteAddressString,businessGroup,uploadBusiness);//将业务加入业务集合
		try {
			return (Boolean)uploadBusiness.executStatus(defaultTimeoutSeconde);
		} catch (Exception e) {
			// TODO: handle exception
			throw e;
		}finally {
			removeCommandBusiness(remoteAddressString, businessGroup);
		}
	}
	
	public boolean fileDelete(String remoteDir){
		if(remoteDir==null) {
			throw new FileBaseException("删除远程文件路径不能为空！", FileBaseException.ERR_PARAM_NULL);
		}
		Channel channel = channelFuture.channel();
		String remoteAddressString=channel.remoteAddress().toString();
		int businessGroup = FileContentApplication.getLastBusinessNumber();
		FileDeleteCommand firstCommad=new FileDeleteCommand(remoteDir, businessGroup);
		ClientFileDeleteBusiness fileDeleteBusiness = new ClientFileDeleteBusiness(firstCommad,channel);
		addCommandBusiness(remoteAddressString,businessGroup,fileDeleteBusiness);//将业务加入业务集合
		try {
			return (boolean)fileDeleteBusiness.executStatus(defaultTimeoutSeconde);
		} catch (Exception e) {
			// TODO: handle exception
			throw e;
		}finally {
			removeCommandBusiness(remoteAddressString, businessGroup);
		}
	}
	
	public boolean mkdir(String remoteDir){
		if(remoteDir==null) {
			throw new FileBaseException("创建文件夹路径不能为空！", FileBaseException.ERR_PARAM_NULL);
		}
		Channel channel = channelFuture.channel();
		String remoteAddressString=channel.remoteAddress().toString();
		int businessGroup = FileContentApplication.getLastBusinessNumber();
		MkdirCommand firstCommad=new MkdirCommand(remoteDir, businessGroup);
		ClientMkdirBusiness mkdirBusiness = new ClientMkdirBusiness(firstCommad,channel);
		addCommandBusiness(remoteAddressString,businessGroup,mkdirBusiness);//将业务加入业务集合
		try {
			return (boolean)mkdirBusiness.executStatus(defaultTimeoutSeconde);
		} catch (Exception e) {
			// TODO: handle exception
			throw e;
		}finally {
			removeCommandBusiness(remoteAddressString, businessGroup);
		}
	}

	/**
	 * 保存内容到
	 * @param data
	 * @param remoteFile
	 * @return
	 */
	public boolean saveData(byte[] data,String remoteFile) {
		if(remoteFile==null || "".equals(remoteFile.trim())) {
			throw new FileBaseException("上传到远程的文件路径不能为空！", FileBaseException.ERR_PARAM_NULL);
		}
		Channel channel = channelFuture.channel();
		String remoteAddressString=channel.remoteAddress().toString();
		int businessGroup = FileContentApplication.getLastBusinessNumber();
		ClientDataUploadBusiness uploadBusiness = new ClientDataUploadBusiness(remoteFile,businessGroup,data,channel);
		addCommandBusiness(remoteAddressString,businessGroup,uploadBusiness);//将业务加入业务集合
		try {
			return (Boolean)uploadBusiness.executStatus(defaultTimeoutSeconde);
		} catch (Exception e) {
			// TODO: handle exception
			throw e;
		}finally {
			removeCommandBusiness(remoteAddressString, businessGroup);
		}
	}
	
	/**
	 * 下载文件
	 * @param remoteFilePath 远程文件路径
	 * @param localFilePath 本地保存文件路径
	 * @return
	 */
	public boolean downloadFile(String remoteFilePath,String localFilePath) {
		if(localFilePath==null || "".equals(localFilePath.trim())) {
			throw new FileBaseException("本地保存的文件路径不能为空！", FileBaseException.ERR_PARAM_NULL);
		}
		if(remoteFilePath==null || "".equals(remoteFilePath.trim())) {
			throw new FileBaseException("下载的远程文件路径不能为空！", FileBaseException.ERR_PARAM_NULL);
		}
		Channel channel = channelFuture.channel();
		String remoteAddressString=channel.remoteAddress().toString();
		int businessGroup = FileContentApplication.getLastBusinessNumber();
		ClientFileDownloadCommand firstCommad=new ClientFileDownloadCommand(remoteFilePath, businessGroup);
		ClientFileDownloadBusiness dowloadBusiness = new ClientFileDownloadBusiness(localFilePath,firstCommad,channel);
		addCommandBusiness(remoteAddressString,businessGroup,dowloadBusiness);//将业务加入业务集合
		try {
			return (Boolean)dowloadBusiness.executStatus(defaultTimeoutSeconde);
		} catch (Exception e) {
			// TODO: handle exception
			throw e;
		}finally {
			removeCommandBusiness(remoteAddressString, businessGroup);
		}
	}
	
	public List<FileDetail> getFileList(String remoteDir){
		
		if(remoteDir==null) {
			throw new FileBaseException("下载的远程目录路径不能为空！", FileBaseException.ERR_PARAM_NULL);
		}
		Channel channel = channelFuture.channel();
		String remoteAddressString=channel.remoteAddress().toString();
		int businessGroup = FileContentApplication.getLastBusinessNumber();
		FileListCommand firstCommad=new FileListCommand(remoteDir, businessGroup);
		ClientFileListBusiness fileListBusiness = new ClientFileListBusiness(firstCommad,channel);
		addCommandBusiness(remoteAddressString,businessGroup,fileListBusiness);//将业务加入业务集合
		try {
			return (List<FileDetail>)fileListBusiness.executStatus(defaultTimeoutSeconde);
		} catch (Exception e) {
			// TODO: handle exception
			throw e;
		}finally {
			removeCommandBusiness(remoteAddressString, businessGroup);
		}
	}
	
	/**
	 * 向业务缓存中添加业务命令
	 * @param remoteAddressString
	 * @param businessGroup
	 * @param bussniess
	 */
	private  static void addCommandBusiness(String remoteAddressString,int businessGroup,ClientBusiness bussniess) {
		businessPackets.put(remoteAddressString+"_"+businessGroup, bussniess);
		logger.log(Level.CONFIG,"缓存业务命令:"+remoteAddressString+"_"+businessGroup);
	}
	
	/**
	 * 向业务缓存中取业务命令
	 * @param remoteAddressString
	 * @param businessGroup
	 */
	public  static ClientBusiness getCommandBusiness(String remoteAddressString,int businessGroup) {
		return businessPackets.get(remoteAddressString+"_"+businessGroup);
	}
	/**
	 * 删除业务缓存中的业务命令
	 * @param remoteAddressString
	 * @param businessGroup
	 */
	private  static void removeCommandBusiness(String remoteAddressString,int businessGroup) {
		try {
			FileContentApplication.clearBusinessNumber(businessGroup);
			businessPackets.remove(remoteAddressString+"_"+businessGroup);
			logger.log(Level.CONFIG,"清理业务命令:"+remoteAddressString+"_"+businessGroup);
		} catch (Exception e) {
			// TODO: handle exception
		} 
		
	}
	/**
	 * 获取默认超时时间
	 * @return
	 */
	public int getDefaultTimeoutSeconde() {
		return defaultTimeoutSeconde;
	}
	/**
	 * 设置默认超时时间
	 * @return
	 */
	public void setDefaultTimeoutSeconde(int defaultTimeoutSeconde) {
		this.defaultTimeoutSeconde = defaultTimeoutSeconde;
	}
	
}
